<?php

/*
 * Copyright (C) 2009 - 2011 Pham Cong Dinh
 *
 * This file is part of Spica.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 3 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */

// namespace spica\core\view;
// classes:
//   spica\core\view\Template
//   spica\core\view\BufferFilter
//   spica\core\view\PageLayout
//   spica\core\view\RenderableAsBlock
//   spica\core\view\BlockFilter
//   spica\core\view\Block
//   spica\core\view\BlockInfo
//   spica\core\view\ContextualBlock
//   spica\core\view\BlockGroup
//   spica\core\view\ContextualBlockGroup
//   spica\core\view\PageNavigation
//   spica\core\view\Widget
//   spica\core\view\TemplateException

// use spica\core\view\html as html;

/**
 * A SpicaTemplate is a software module which is used to generate HTML pages
 * (or other text files). The layout of a HTML page is defined within a template
 * file, which can be designed by using a HTML editor like Dreamweaver, Netbeans.
 * At run-time, the application program instructs the template system to load the
 * template, fill in variable values, repeat blocks and produce the final HTML page.
 *
 * This class is partially ported from Pone_View (PONE framework).
 *
 * @category   spica
 * @package    core
 * @subpackage view
 * @author     Pham Cong Dinh <pcdinh at phpvietnam dot net>
 * @since      Version 0.2
 * @since      March 16, 2009
 * @copyright  Pham Cong Dinh (http://www.phpvietnam.net)
 * @license    http://www.gnu.org/licenses/lgpl-3.0.txt
 * @version    $Id: Template.php 1869 2011-01-07 18:55:25Z pcdinh $
 */
class SpicaTemplate
{
    /**
     * Page title.
     *
     * @var string
     */
    public $pageTitle = 'Untitled page';

    /**
     * Escape mode parameter to HTML escape functions.
     *
     * @see SpicaTemplate#escape()
     * @var int
     */
    protected $_escapeMode = ENT_COMPAT;

    /**
     * Charset parameter to HTML escape functions.
     *
     * @see SpicaTemplate#escape()
     * @var int
     */
    protected $_escapeCharset = 'UTF-8';

    /**
     * Variables to be extracted within a partial.
     *
     * @var array
     */
    protected $_partialVars;

    /**
     * Partial files.
     *
     * @var array
     */
    protected $_partialFiles;

    /**
     * Set of template files (specs: fileName => full path)
     *
     * @var array
     */
    protected $_templateFiles;

    /**
     * Full path to template file (including directory path and its extension).
     *
     * @see SpicaTemplate::_templateFile
     * @var string
     */
    protected $_templateFilePath;

    /**
     * The template file name (not including file extension) that is used to render.
     *
     * @var string
     */
    protected $_templateFile;

    /**
     * Template file extension. E.x: .tpl.php
     *
     * @var string
     */
    protected $_templateExt;

    /**
     * Base path to template files.
     *
     * @see SpicaTemplate#getTemplateFile()
     * @var string
     */
    protected $_basePath;

    /**
     * Template directory name.
     *
     * @var string
     */
    protected $_templateDirName = 'page';

    /**
     * Stack of SpicaTemplateOutputFilter object to perform filtering on buffer content.
     *
     * @see SpicaTemplate#fetch()
     * @see SpicaTemplateOutputFilter
     * @var array|null An array of SpicaTemplateOutputFilter objects - array key can be a number or a string that represents a class name
     */
    protected $_outputFilters;

    /**
     * List of loaded widgets.
     * 
     * @var null|array An array whose key is the class name and value is its template path
     */
    protected static $_loadedWidgets;

    /**
     * Constructs an object of <code>SpicaTemplate</code>.
     *
     * @param string $fileExt Defaults to '.tpl.php'
     * @param string $basePath Defaults to current directory
     */
    public function __construct($fileExt = '.tpl.php', $basePath = '.')
    {
        $this->_templateExt = $fileExt;
        $this->_basePath    = $basePath;
    }

    /**
     * Sets base path to template files.
     *
     * @param string $path
     */
    public function setBasePath($path)
    {
        $this->_basePath = $path;
    }

    /**
     * Directly assigns a variable to the view script.
     *
     * Setting of underscore-prefixed variables is disallowed.
     *
     * @throws SpicaTemplateException
     * @param  string $key The variable name.
     * @param  string $val The variable value.
     */
    public function __set($key, $val)
    {
        // Assign values to private variable is not allowed
        if ('_' !== $key[0])
        {
            $this->$key = $val;
            return;
        }

        throw new SpicaTemplateException('Setting underscore-prefixed variables is not allowed. See: '.$key);
    }

    /**
     * Clears all assigned variables.
     *
     * Clears all variables assigned to SpicaTemplate via property overloading ({@link __set()}).
     */
    public function clearVars()
    {
        $vars = get_object_vars($this);

        foreach ($vars as $key => $value)
        {
            if ('_' !== substr($key, 0, 1))
            {
                unset($this->$key);
            }
        }
    }

    /**
     * Sets encoding to use with <code>htmlentities()</code> and <code>htmlspecialchars()</code>.
     *
     * @see    SpicaTemplate#escape()
     * @param  string $encoding
     */
    public function setEncoding($encoding)
    {
        $this->_escapeCharset = $encoding;
    }

    /**
     * Adds a filter to perform filtering on the buffer generated by evaluating a template file.
     *
     * @param SpicaTemplateOutputFilter $filter
     * @param string $kclass Specific class name that the output filter is installed for. Default to <code>null</code>
     */
    public function addOutputFilter($filter, $kclass = null)
    {
        if (null === $kclass)
        {
            $this->_outputFilters[] = $filter;
        }
        else
        {
            $this->_outputFilters[$kclass][] = $filter;
        }
    }

    /**
     * Removes filter stack.
     *
     * @see SpicaTemplate#addOutputFilter()
     */
    public function unsetOutputFilters()
    {
        $this->_outputFilters = null;
    }

    /**
     * Gets registered output filters.
     * 
     * @return null|array
     */
    public function getOutputFilters()
    {
        return $this->_outputFilters;
    }

    /**
     * Escapes output.
     *
     * @param  string|int $value The value to escape.
     * @return string The escaped value.
     */
    public function escape($value)
    {
        return htmlspecialchars($value, $this->_escapeMode, $this->_escapeCharset);
    }

    /**
     * Sets template directory name.
     *
     * @param  string $name Directory name. Mostly: layout, page, widget, block, blockgroup, partial
     * @return string
     */
    public function setTemplateDirName($name)
    {
        $this->_templateDirName = str_replace(array('..', '%', "\0"), '', $name);
    }

    /**
     * Displays a template directly.
     *
     * @param string $name The template to display.
     */
    public function render($name)
    {
        echo $this->fetch($name);
    }

    /**
     * Fetches template output.
     *
     * @param  string $name The template to process.
     * @return string The template output.
     */
    public function fetch($name)
    {
        $type = $this->_templateDirName;
        // find the absolute path of the file by its conventional name
        $this->_templateFilePath = $this->getTemplateFile($name, $type);
        return $this->fetchFile($this->_templateFilePath);
    }

    /**
     * Fetches the content of the template file specified by $path.
     * 
     * @param string $path Absolute template file path
     * @return string The template output.
     */
    public function fetchFile($path)
    {
        try
        {
            ob_start();
            require $path;
        }
        catch (Exception $e)
        {
            Spica::logException($e);
            ob_end_clean();

            if (false === SpicaContext::isDebug())
            {
                $path = spica_path_make_relative($path);
            }
            
            return 'An '.get_class($e).' is thrown while fetching '.$path.': '.spica_path_make_relative($e->getFile()).' at '.$e->getLine();
        }

        if (!empty($this->_outputFilters))
        {
            return $this->_filterBuffer(ob_get_clean()); // filter output
        }

        return ob_get_clean();
    }

    /**
     * Gets absolute path of the template file by its name.
     *
     * @throws SpicaTemplateException in case template file can not be found.
     * @param  string $name Template file name without extension
     * @param  string $type Template file type: page, layout, partial, block, blockgroup, widget
     * @return string
     */
    public function getTemplateFile($name, $type = 'page')
    {
        // Secure file name (dot-dot-slash/directory traversal/directory climbing attack)
        $name = str_replace(array("\0", '..', '%'), '', $name);
        // Already processed
        if (false !== isset($this->_templateFiles[$name]))
        {
            return $this->_templateFiles[$name];
        }

        // Subfolders in theme directory (page/, layout/, partial/, block/, blockgroup/, widget/)
        $directory = $type.'/';
        $filePath  = $this->_basePath.'/'.$directory.$name.'.'.trim($this->_templateExt, '.');

        if (false  === file_exists($filePath))
        {
            $path  = (true === SpicaContext::isDebug()) ? $filePath : spica_path_make_relative($filePath);
            throw new SpicaTemplateException('Template file named '.$name.' can not be found at "'.$path.'". ');
        }

        $this->_templateFiles[$name] = $filePath;
        return $filePath;
    }

    /**
     * Unsets the widget registry.
     *
     * @internal Used for unit testing
     * @see SpicaTemplate#createWidget()
     */
    public function unsetWidgets()
    {
        self::$_loadedWidgets = null;
    }

    /**
     * Gets the output content of a widget.
     *
     * @throws InvalidArgumentException if file path to widget class can not be found
     * @param  string $name   Widget name. Format: name.ClassName
     * @param  array  $params Widget parameters
     * @param  string $templatePath Template path, a directory path or absolute file path
     * @return string
     */
    public function fetchWidget($name, $params = array(), $templatePath = null)
    {
        return $this->createWidget($name, $templatePath)->output($params);
    }

    /**
     * Gets an instance of a widget.
     *
     * This method is used by SpicaTemplate::fetchWidget() and should be avoided to use directly
     *
     * @see    SpicaTemplate#fetchWidget()
     * @throws InvalidArgumentException if file path to widget class can not be found
     * @param  string $name Name of the widget name whose format is <code>name.ClassName</code>
     *                 where "name" can be a number, ASCII characters and dash; "name"
     *                 can be library namespace such as "spica" or "spicax"
     * @param  string $templatePath Template path, if template path is a directory, template file will be sanem name to widget class
     * @return SpicaWidget
     */
    public function createWidget($name, $templatePath = null)
    {
        include_once 'library/spica/core/view/ui/Widget.php';

        $names = explode('.', $name);
        // I do not check class name for validity because invalid class name will
        // result in an exception without any affect to security
        $class = array_pop($names);

        if (isset(self::$_loadedWidgets[$class]))
        {
            return new $class(self::$_loadedWidgets[$class]);
        }

        // Spica's built-in widget is loaded with "spica." prefix by convention
        if (isset($names[0]) && 'spica' === $names[0])
        {
            $path = SpicaContext::getBasePath() . '/library/spica/core/view/ui';
        }
        else
        {
            $path = spica_widget_classpath() . implode('/', $names);
        }

        ob_start();
        include_once $path.'/'.$class.'.php';
        ob_end_clean();

        if (false === class_exists($class))
        {
            throw new InvalidArgumentException('Unable to instantiate the widget named '.$name);
        }

        if (null !== $templatePath)
        {
            if (is_dir($templatePath))
            {                
                $templatePath = $templatePath . '/' . $class.'.tpl.php';
            }
        }
        else
        {
            $templatePath = $path . '/' . $class.'.tpl.php';
        }

        self::$_loadedWidgets[$class] = $templatePath;
        return new $class($templatePath);
    }

    /**
     * Gets fetched template full qualified file path.
     *
     * @return string
     */
    public function getFetchedTemplateFilePath()
    {
        return $this->_templateFilePath;
    }

    /**
     * Gets fetched template file (not including path and extension).
     *
     * @return string
     */
    public function getFetchedTemplateFile()
    {
        return $this->_templateFile;
    }

    /**
     * Checks if the template name in question exists.
     *
     * @param  string $name The template name in question.
     * @return bool
     */
    public function templateExists($name)
    {
        try
        {
            // check the absolute path of the file by its conventional name
            $this->getTemplateFile($name, $this->_templateDirName);
            return true;
        }
        catch (SpicaTemplateException $e)
        {
            return false;
        }
    }

    /**
     * Executes a partial template in its own scope, optionally with
     * variables into its within its scope.
     *
     * Note that when you don't need scope separation, using a call to
     * "include $this->template($name)" is faster.
     *
     * @param string $__name The partial template to process. It is a file name without extension.
     * @param array|object $spec Additional variables to use within the
     * partial template scope. If an array, we use extract() on it.
     * If an object, we create a new variable named after the partial template
     * file and set that new variable to be the object.
     * @return string The output of the partial template script.
     */
    public function partial($__name, $spec = null)
    {
        // $__name is a bit special name but it prevents name conflicts with
        // variables in partial templates
        $filePath = $this->getTemplateFile($__name, 'partial');
        $this->_partialFiles[$__name] = $filePath;

        // save partial vars externally. special cases for different types.
        // keep vars as an array to be extracted
        $this->_partialVars[$__name] = (array) $spec;

        // remove the spec and file path from local scope
        unset($spec);
        unset($filePath);

        // disallow resetting of $this
        unset($this->_partialVars['this']);

        // inject vars into local scope
        extract($this->_partialVars[$__name]);

        // evaluate the partial template
        ob_start();
        require $this->_partialFiles[$__name];
        return ob_get_clean();
    }

    /**
     * Performs filtering on a buffer.
     *
     * @see    SpicaTemplate#addOutputFilter()
     * @see    SpicaTemplate#fetch()
     * @param  string $buffer The buffer contents.
     * @return string The filtered buffer.
     */
    private function _filterBuffer($buffer)
    {        
        $kclass = get_class($this); // current class name of the template object        

        /**
         * If a output filter is installed for a specific class, apply it
         */
        if (isset($this->_outputFilters[$kclass]))
        {            
            // Class filter
            $cfilters = $this->_outputFilters[$kclass];

            foreach ($cfilters as $filter)
            {
                $buffer = $filter->doFilter($buffer);
            }

            unset($this->_outputFilters[$kclass]);
        }
        
        // loop through each filter object
        foreach ($this->_outputFilters as $class => $filter)
        {
            // If $class is a string, it is not a general filter
            if (!is_string($class))
            {
                $buffer = $filter->doFilter($buffer);
            }
        }

        return $buffer;
    }
}

/**
 * A PageBlockFilter filter is an object that performs filtering tasks before
 * a page block object call its initialize() method.
 *
 * Filters perform filtering in the doFilter method
 *
 * @category   spica
 * @package    core
 * @subpackage view
 * @author     Pham Cong Dinh <pcdinh at phpvietnam dot net>
 * @since      Version 0.2
 * @since      March 25, 2009
 * @copyright  Pham Cong Dinh (http://www.phpvietnam.net)
 * @license    http://www.gnu.org/licenses/lgpl-3.0.txt
 * @version    $Id: Template.php 1869 2011-01-07 18:55:25Z pcdinh $
 */
interface SpicaPageBlockFilter
{
    /**
     * The doFilter method of the SpicaPageBlockFilter is called by
     * a SpicaPageBlock object before initialize() method is called.
     *
     * @param SpicaPageBlock $block The page block on filtering
     */
    public function doFilter(SpicaPageBlock $block);
}

/**
 * A template output filter performs filtering tasks on the static content
 * outputed from a template file.
 *
 * Filters perform filtering in the <code>doFilter()</code> method. This method can be
 * used to do caching or inject necessary HTML snippets. The template object that produces the
 * output can be identified by calling <code>get_class($this)</code>
 *
 * @category   spica
 * @package    core
 * @subpackage view
 * @author     Pham Cong Dinh <pcdinh at phpvietnam dot net>
 * @since      Version 0.2
 * @since      March 24, 2009
 * @copyright  Pham Cong Dinh (http://www.phpvietnam.net)
 * @license    http://www.gnu.org/licenses/lgpl-3.0.txt
 * @version    $Id: Template.php 1869 2011-01-07 18:55:25Z pcdinh $
 */
interface SpicaTemplateOutputFilter
{
    /**
     * The <code>doFilter()</code> method of the <code>SpicaTemplateOutputFilter</code> is called by a template object
     * after it evaluates a template file.
     *
     * @param  string $output The static content generated by evaluating a template file
     * @return string Output after being filtered
     */
    public function doFilter($output);
}

/**
 * Allows any template object to act as a <code>SpicaPageBlock</code>
 *
 * @category   spica
 * @package    core
 * @subpackage view
 * @author     Pham Cong Dinh <pcdinh at phpvietnam dot net>
 * @since      Version 0.2
 * @since      March 16, 2009
 * @copyright  Pham Cong Dinh (http://www.phpvietnam.net)
 * @license    http://www.gnu.org/licenses/lgpl-3.0.txt
 * @version    $Id: Template.php 1869 2011-01-07 18:55:25Z pcdinh $
 */
interface SpicaTemplateRenderableAsBlock
{
    /**
     * Handles before the block evaluates its template content to the caller.
     * This method is executed before {SpicaTemplateRenderableAsBlock#output()}
     */
    public function initialize();

    /**
     * Evaluates and returns static content output to the caller
     * but not show the content up to the browser.
     *
     * @return string
     */
    public function output();
}

/**
 * Contains meta information about a particular block which can be retrieved
 * via info() method.
 *
 * @category   spica
 * @package    core
 * @subpackage view
 * @author     Pham Cong Dinh <pcdinh at phpvietnam dot net>
 * @since      Version 0.2
 * @since      March 20, 2009
 * @copyright  Pham Cong Dinh (http://www.phpvietnam.net)
 * @license    http://www.gnu.org/licenses/lgpl-3.0.txt
 * @version    $Id: Template.php 1869 2011-01-07 18:55:25Z pcdinh $
 */
class SpicaPageBlockInfo
{
    /**
     * Page block class name.
     *
     * @var string
     */
    public $className;

    /**
     * Page block author.
     *
     * @var string
     */
    public $author;

    /**
     * The date that the block was released.
     *
     * @var string
     */
    public $releaseDate;

    /**
     * The template file name.
     *
     * @var string
     */
    public $template;

    /**
     * Page block pre-conditions.
     *
     * @var array
     */
    public $dependencies = array();

    /**
     * Page block version.
     *
     * @var string
     */
    public $version;
}

/**
 * A block is an application-level content component (E.x link section,
 * new article list section ..) that is displayed on your web page.
 *
 * This class is partially ported from Pone_View_Element (PONE framework).
 *
 * @category   spica
 * @package    core
 * @subpackage view
 * @author     Pham Cong Dinh <pcdinh at phpvietnam dot net>
 * @since      Version 0.2
 * @since      March 16, 2009
 * @copyright  Pham Cong Dinh (http://www.phpvietnam.net)
 * @license    http://www.gnu.org/licenses/lgpl-3.0.txt
 * @version    $Id: Template.php 1869 2011-01-07 18:55:25Z pcdinh $
 */
abstract class SpicaPageBlock extends SpicaTemplate implements SpicaTemplateRenderableAsBlock
{
    /**
     * Template directory name.
     *
     * @var string
     */
    protected $_templateDirName = 'block';

    /**
     * The prefix part of the page block class names.
     *
     * @var string
     */
    protected $_classSuffix = 'Block';

    /**
     * Is this block allowed to output its template?
     *
     * @var bool
     */
    public static $renderable = true;

    /**
     * Stack of SpicaPageBlockFilter object to perform filtering
     * before initialize() is invoked.
     *
     * @see SpicaTemplate#initialize()
     * @var array Array[:SpicaPageBlockFilter]
     */
    protected $_filters = array();

    /**
     * Cached output.
     *
     * @see SpicaPageBlock#output()
     * @see SpicaPageBlock#beforeInitialization()
     * @see SpicaPageBlock#_filter()
     * @var string
     */
    protected $_cachedOutput;

    /**
     * The value indicating whether this object supports filtering.
     *
     * @see SpicaPageBlock#beforeInitialization()
     * @see SpicaPageBlockFilter
     * @var bool
     */
    protected $_filterable = false;

    /**
     * The value indicating whether this template object needs to allocates resources
     * prior to it becoming renderable.
     *
     * @var bool
     */
    protected $_initializable = true;

    /**
     * Constructs an object of <code>SpicaPageBlock</code>
     *
     * @param string $fileExt  Defaults to '.tpl.php'
     * @param string $basePath Base path to template file. Defaults to the current directory
     * @param string $theme    Theme name. Defaults to null
     */
    public function __construct($fileExt = '.tpl.php', $basePath = '.')
    {
        $this->_templateExt = $fileExt;
        $this->_basePath    = $basePath;
        $this->beforeInitialization();

        if (true === $this->_filterable)
        {
            $this->_filter();
        }

        if (true === $this->_initializable)
        {
            $this->initialize();
        }
    }

    /**
     * Hook to perform action before SpicaPageBlock::_filter() and SpicaPageBlock::initialize()
     * are called.
     *
     * It is often used by PageBlock developer to register SpicaPageBlockFilter
     * objects for used with filter chain.
     */
    public function beforeInitialization()
    {
        // no-op
    }

    /**
     * (non-PHPdoc)
     * @see library/spica/core/view/SpicaTemplateRenderableAsBlock#initialize()
     */
    public function initialize()
    {
        // no-op
    }

    /**
     * (non-PHPdoc)
     * @see library/spica/core/view/SpicaTemplateRenderableAsBlock#output()
     */
    public function output()
    {
        if (false === self::$renderable)
        {
            return null;
        }

        if (null !== $this->_cachedOutput)
        {
            return $this->_cachedOutput;
        }

        if (null === $this->_templateFile)
        {
            $temp  = str_replace($this->_classSuffix, '', get_class($this));
            $first = strtolower(substr($temp, 0, 1));
            $rest  = substr($temp, 1);
            // Default template name is used
            $this->_templateFile = $first.$rest;
        }

        try
        {
            return $this->fetch($this->_templateFile);
        }
        catch (Exception $ex)
        {
            return $ex->getMessage();
        }
    }

    /**
     * Performs filtering on the current SpicaPageBlock object before SpicaPageBlock's
     * initialize() is called.
     *
     * @see SpicaPageBlockFilter
     */
    protected function _filter()
    {
        // loop through each filter object
        foreach ($this->_filters as $filter)
        {
            $filter->doFilter($this);
        }
    }

    /**
     * Gets meta information about this block.
     *
     * This method is called by automated system to retrieve its meta information
     * that will be saved into a database later for registration purpose. Imagine that
     * we design a slot-based layout system that can be configured via web form
     * that interacts with database backend. The system will read page block information
     * to determine if the block can be assigned into the slot.
     *
     * This method is designed to be overridden in derived classes.
     *
     * @return SpicaPageBlockInfo
     */
    public function info()
    {
        return new SpicaPageBlockInfo();
    }
}

/**
 * A contextual block is an view element component within a page, like a page block.
 * However, it is different from a normal block because it does not render the content itself.
 * Instead, its interfaces with the environment in which it operates to see
 * which ordinary block shoud do the job on behalf of it. Therefore, it outputs the name
 * of the page block that is configured to return the content displayed to the user.
 *
 * This class is partially ported from Pone_View_ContextualElement (PONE framework).
 *
 * @category   spica
 * @package    core
 * @subpackage view
 * @author     Pham Cong Dinh <pcdinh at phpvietnam dot net>
 * @since      Version 0.2
 * @since      March 19, 2009
 * @copyright  Pham Cong Dinh (http://www.phpvietnam.net)
 * @license    http://www.gnu.org/licenses/lgpl-3.0.txt
 * @version    $Id: Template.php 1869 2011-01-07 18:55:25Z pcdinh $
 */
abstract class SpicaContextualPageBlock extends SpicaPageBlock
{
    /**
     * An array where context tokens act as keys and element names act as values.
     *
     * @var array
     */
    protected $_contextMap = array();

    /**
     * The element name in use in the current context.
     *
     * @var string
     */
    private $_contextualName;

    /**
     * Detects the current context token and assign the relevant element name.
     *
     * This is a framework-invoked method; it should not normally be called directly.
     */
    public function initialize()
    {
        $this->setup();
        $token = $this->getContextToken();

        if (true === isset($this->_contextMap[$token]))
        {
            $this->_contextualName = $this->_contextMap[$token];
        }
        else
        {
            $this->_contextualName = $this->getDefaultName();
        }
    }

    /**
     * Gets context token based on request components.
     *
     * @return string
     */
    public function getContextToken()
    {
        return SpicaContext::getRequestBaseId();
    }

    /**
     * Gets the default element name when there is no official name for the current
     * context that is defined in the context map variable.
     *
     * This method is invoked automatically when the current context is not defined
     * in the context map.
     * It is designed to allow developers have more rooms in adding more complex
     * logic code to detect element name based on the current context.
     * E.x Element name can be retrieved from the database.
     *
     * While $_contextMap is designed to define content-element map statically,
     * this method is aimed to define content-element map dynamically.
     *
     * This is a framework-invoked method; it should not normally be called directly.
     *
     * @return string
     */
    public abstract function getDefaultName();

    /**
     * Configures contextual block itself before context token is analyzed.
     * This method is no-op by default. Developer can override it in the implementing classes.
     *
     * @see SpicaContextualPageBlock#initialize()
     */
    public function setup()
    {
        // no-op
    }

    /**
     * Returns the name of the page block that is configured to display its template output.
     *
     * This is a framework-invoked method; it should not normally be called directly.
     *
     * @return string|false
     */
    public function output()
    {
        return $this->_contextualName;
    }

    /**
     * (non-PHPdoc)
     * @see library/spica/core/view/SpicaTemplate#fetch()
     */
    public final function fetch($name)
    {
        throw new BadMethodCallException('The derived classes of SpicaContextualPageBlockGroup are not intended to render a template output. ');
    }
}

/**
 * A block group is a collection of static text and page blocks, derived
 * from a component template.
 *
 * This class is partially ported from Pone_View_ElementGroup (PONE framework).
 *
 * @category   spica
 * @package    core
 * @subpackage view
 * @author     Pham Cong Dinh <pcdinh at phpvietnam dot net>
 * @since      Version 0.2
 * @since      March 16, 2009
 * @copyright  Pham Cong Dinh (http://www.phpvietnam.net)
 * @license    http://www.gnu.org/licenses/lgpl-3.0.txt
 * @version    $Id: Template.php 1869 2011-01-07 18:55:25Z pcdinh $
 */
abstract class SpicaPageBlockGroup extends SpicaPageBlock
{
    /**
     * Template directory name.
     *
     * @var string
     */
    protected $_templateDirName = 'blockgroup';

    /**
     * The prefix part of the page block group class names
     *
     * @var string
     */
    protected $_classSuffix = 'BlockGroup';

    /**
     * Set of class names of page blocks that are configured to display their content
     * within the template of the current block group. Any block name whose name
     * is not here, it can not be rendered even there is an explicit call in the
     * block group template file.
     *
     * @see SpicaPageBlockGroup#registerBlocks()
     * @see SpicaPageBlockGroup#fetchBlock()
     * @var array
     */
    protected $_blocks = array();

    /**
     * Fetched block names.
     *
     * @var array
     */
    protected $_fetched = array();

    /**
     * If this value is true, any call to fetchBlock($name) in template files will be checked
     * to see if $name is registered with the its host blockgroup or not.
     *
     * @var bool
     */
    protected $_legalCheck = true;

    /**
     * Content of blocks that is prepared at the first place without the need to create
     * block object, execute block methods and evaluate block template.
     *
     * @var array Block name as key and its string content as value
     */
    public $precompiledContent = array();

    /**
     * Constructs an object of <code>SpicaPageBlockGroup</code>.
     *
     * @param string $fileExt  Defaults to '.tpl.php'
     * @param string $basePath Base path to template file.
     */
    public function __construct($fileExt = '.tpl.php', $basePath = '.')
    {
        $this->_templateExt = $fileExt;
        $this->_basePath    = $basePath;
        $this->beforeInitialization();

        if (true === $this->_filterable)
        {
            $this->_filter();
        }

        if (true === $this->_initializable)
        {
            $this->registerBlocks();
            $this->initialize();
        }
    }

    /**
     * Hooks to registers class names of the blocks that are allowed to
     * include into the block group template;
     *
     * This is a framework-invoked method; it should not normally be called directly;
     *
     * @see SpicaPageBlockGroup::_blocks
     */
    public function registerBlocks()
    {
        // no-op by default
    }

    /**
     * Fetches template output of a "block" of content in a page;
     * A block is always owned by a module, which is responsible for feeding
     * the block with the appropriate information to produce its output;
     *
     * This method will trigger an event named 'spica_block_fetch' and pass 2 arguments
     * to its registered callbacks: block name and this instance
     *
     * @param  string $name Class name of the block. E.x: "default/SampleBlock" or "SampleBlock"
     * @return string
     */
    public function fetchBlock($name)
    {
        if (true === $this->_legalCheck && false === in_array($name, $this->_blocks))
        {
            return false;
        }

        Spica::publishEvent('spica_block_fetch', $name, $this);
        // Above event can trigger one or more callback/closures that can
        // possibly prepare precompiled data (or cached data) for this block
        if (true === isset($this->precompiledContent[$name]))
        {
            return $this->precompiledContent[$name];
        }

        try
        {
            $block = $this->getBlock($name);
            $this->_fetched[] = $name;
            return $block->output();
        }
        catch (Exception $e)
        {
            Spica::logException($e);
            return get_class($e).': '.$e->getMessage();
        }
    }

    /**
     * Gets an object of <code>SpicaPageBlock</code> by a conventional name.
     *
     * @throws InvalidArgumentException if block class can not be found or block name is not valid
     * @param  string $name Name of the class that represents the page block.
     * @return SpicaPageBlock
     */
    public function getBlock($name)
    {
        // Secure file name
        if (false === ctype_alnum(str_replace(array('/', '_'), '', $name)))
        {
            throw new InvalidArgumentException('Invalid block name: '.$name);
        }

        $file = spica_block_classpath().'/'.$name.'.php';
        // I don't want to check file existence first because it is not optimistic.
        // It would assume that developers always did something wrong, which was
        // correct all the time. If file does not exist a error log will be recorded
        ob_start();
        include_once $file;
        ob_end_clean();
        // Find class name
        $name = substr(basename($file), 0, -4);

        if (false === class_exists($name))
        {
            $path = (true === SpicaContext::isDebug()) ? $file : spica_path_make_relative($file);
            throw new InvalidArgumentException('Class named '.$name.' can not be found in block directory. Path: '.$path);
        }

        return new $name('.tpl.php', $this->_basePath);
    }

    /**
     * Fetches template output of a "contextual block" of content in a page.
     * A contextual block does not have its own template output. Instead, it delegates
     * the rendering task to a relevant block that is configured to do.
     *
     * @param  string $name
     * @return string
     */
    public function fetchContextualBlock($name)
    {
        // Name of the block that is configured to display the actual content.
        $this->fetchBlock($this->fetchBlock($name));
    }

    /**
     * Gets fetched block names.
     *
     * @return array
     */
    public function getFetchedBlocks()
    {
        return $this->_fetched;
    }
}

/**
 * A contextual block group is an view element component within a page, like a
 * page block group. However, it is different from a normal block group because
 * it does not render the content itself. Instead, its interfaces with the
 * environment in which it operates to see which ordinary block group shoud do
 * the job on behalf of it. Therefore, it outputs the name of the page block
 * group that is configured to return the content displayed to the user.
 *
 * This class is partially ported from Pone_View_ContextualElementGroup (PONE framework).
 *
 * @category   spica
 * @package    core
 * @subpackage view
 * @author     Pham Cong Dinh <pcdinh at phpvietnam dot net>
 * @since      Version 0.2
 * @since      March 19, 2009
 * @copyright  Pham Cong Dinh (http://www.phpvietnam.net)
 * @license    http://www.gnu.org/licenses/lgpl-3.0.txt
 * @version    $Id: Template.php 1869 2011-01-07 18:55:25Z pcdinh $
 */
abstract class SpicaContextualPageBlockGroup extends SpicaContextualPageBlock {}

/**
 * A SpicaPageLayout is a software module which is used to generate HTML pages
 * (or other text files). The layout of a HTML page is defined within a template file,
 * which can be designed by using a HTML editor like Dreamweaver, Netbeans.
 * At run-time, the application program instructs the template system to load
 * the template, fill in variable values, repeat blocks and produce the final HTML page.
 *
 * This class is partially ported from Pone_View_Layout (PONE framework).
 *
 * @category   spica
 * @package    core
 * @subpackage view
 * @author     Pham Cong Dinh <pcdinh at phpvietnam dot net>
 * @since      Version 0.2
 * @since      March 16, 2009
 * @copyright  Pham Cong Dinh (http://www.phpvietnam.net)
 * @license    http://www.gnu.org/licenses/lgpl-3.0.txt
 * @version    $Id: Template.php 1869 2011-01-07 18:55:25Z pcdinh $
 */
class SpicaPageLayout extends SpicaPageBlockGroup
{
    /**
     * Template directory name.
     *
     * @var string
     */
    protected $_templateDirName = 'layout';

    /**
     * Set of placeholders in the current page layout.
     *
     * @var array
     */
    protected $_placeholders = array();

    /**
     * Set of page blocks in the current page layout.
     *
     * @var array
     */
    protected $_blocks = array();

    /**
     * Set of page block groups in the current page layout.
     *
     * @var array
     */
    protected $_blockGroups = array();

    /**
     * A template object that can be a page block or page block group.
     *
     * @var SpicaTemplateRenderableAsBlock
     */
    protected $_body;

    /**
     * Block or block group class name whose instance is used to
     * output page body content.
     *
     * @var string
     */
    protected $_bodyClass;

    /**
     * Files to includes CSS style sheets or <link> tags.
     *
     * @var array
     */
    protected $_cssFiles = array();

    /**
     * Files to includes <script> tags or Javascript code.
     *
     * @var array
     */
    protected $_scriptFiles = array();

    /**
     * Files to include <meta> tags.
     *
     * @var array
     */
    protected $_metaFiles = array();

    /**
     * The name of the current theme direcory in use
     *
     * @var string
     */
    protected $_theme;

    /**
     * If this value is true, block and block group can be fetched without registeration
     * with host class.
     *
     * @var bool
     */
    protected $_legalCheck = false;

    /**
     * The output filter that works on all the whole page content, not page blocks or blockgroups.
     *
     * If you use SpicaPageLayout::addOutputFilter(), the filters will works on every blocks, partials
     * or template that are evaluated by the page layout object each time you call fetch(), fetchBody(), fetchBlock().
     *
     * However page output filter will work on the final page content only.
     *
     * @var array
     */
    protected $_pageFilters;

    /**
     * Constructs an object of <code>SpicaPageLayout</code>
     *
     * @param string $fileExt Defaults to '.tpl.php'
     * @param string $basePath Base path to template file. Defaults to the current directory
     * @param string $theme    Theme name. Defaults to null
     */
    public function __construct($fileExt = '.tpl.php', $basePath = '.', $theme = null)
    {
        $this->_theme = $theme;
        parent::__construct($fileExt, $basePath.'/'.$theme);
    }

    /**
     * (non-PHPdoc)
     * @see library/spica/core/view/SpicaTemplate#fetch()
     */
    public function fetch($file = 'index')
    {       
        $c = parent::fetch($file);

        if (!empty($this->_pageFilters))
        {
            foreach ($this->_pageFilters as $filter)
            {
                $c = $filter->doFilter($c);
            }
        }

        return $c;
    }

    /**
     * Gets the content output of a page that normally located in /page directory.
     *
     * @param  string $file Base file name
     * @return string
     */
    public function fetchPage($file = 'index')
    {
        // find the absolute path of the file by its conventional name
        $this->_templateFilePath = $this->getTemplateFile($file, 'page');
        return $this->fetchFile($this->_templateFilePath);
    }

    /**
     * Fetches template output of a "block" of content in a page.
     * A block is always owned by a module, which is responsible for feeding
     * the block with the appropriate information to produce its output.
     *
     * @see    SpicaPageBlockGroup#fetchBlock()
     * @param  string $name
     * @return string
     */
    public function fetchBlock($name)
    {
        return parent::fetchBlock($name);
    }

    /**
     * Gets an object of <code>SpicaPageBlockGroup</code> by a conventional name.
     *
     * @throws InvalidArgumentException
     * @param  string $name Class name of the block group. E.x: "default/SampleBlockGroup" or "SampleBlockGroup"
     * @return SpicaPageBlockGroup
     */
    public function getBlockGroup($name)
    {
        // Secure file name
        if (false === ctype_alnum(str_replace(array('/', '_'), '', $name)))
        {
            throw new InvalidArgumentException('Invalid block group name: '.$name);
        }

        $file = spica_blockgroup_classpath().'/'.$name.'.php';
        ob_start();
        // Always assuming that file exists but we will check class existence then.
        include_once $file;
        ob_end_clean();
        // Find class name
        $name = substr(basename($file), 0, -4);

        // Optimistic checking: @see Also SpicaPageBlockGroup#getBlock()
        if (false === class_exists($name))
        {
            $path = (true === SpicaContext::isDebug()) ? $file : spica_path_make_relative($file);
            throw new InvalidArgumentException('Class named '.$name.' cannot be found in blockgroup directory. Path: '.$path);
        }

        return new $name('.tpl.php', $this->_basePath);
    }

    /**
     * Fetches template output of a group of blocks; A block group is the union
     * of one or more blocks; This method will trigger an event
     * named 'spica_blockgroup_fetch' and pass 2 arguments to its registered
     * callbacks: block group name and this instance
     *
     * @throws InvalidArgumentException
     * @param  string $name
     * @return string
     */
    public function fetchBlockGroup($name)
    {
        Spica::publishEvent('spica_blockgroup_fetch', $name, $this);

        // Above event can trigger one or more callback/closures that can
        // possibly prepare precompiled data (or cached data) for this block
        if (true === isset($this->precompiledContent[$name]))
        {
            return $this->precompiledContent[$name];
        }

        try
        {
            $block = $this->getBlockGroup($name);
            $this->_blockGroups[] = $name;
            return $block->output();
        }
        catch (Exception $e)
        {
            Spica::logException($e);
            return get_class($e).': '.$e->getMessage();
        }
    }

    /**
     * Fetches template output of a "contextual block group" of content in a page.
     * A contextual block group does not have its own template output.
     * Instead, it delegates the rendering task to a relevant block group
     * that is configured to do.
     *
     * @param  string $name
     * @return string
     */
    public function fetchContextualBlockGroup($name)
    {
        // Name of the block group that is configured to display the actual content.
        $class = $this->fetchBlockGroup($name);
        // Exception message
        if (strpos($class, ':'))
        {
            return $class;
        }

        return $this->fetchBlockGroup($class);
    }

    /**
     * Registers a class that is responsible for providing page body content.
     *
     * @see    SpicaPageLayout#fetchBody()
     * @param  string $className Names must end with Block or BlockGroup
     */
    public function setBody($className)
    {
        $this->_bodyClass = $className;
    }

    /**
     * Gets name of the class that is responsible for providing page body content.
     *
     * @see    SpicaPageLayout#setBody()
     * @see    SpicaPageLayout#fetchBody()
     * @return string
     */
    public function getBodyClass()
    {
        return $this->_bodyClass;
    }

    /**
     * Gets the object of block or block group that is registered to provide
     * page body content in the page layout.
     *
     * @throws Exception
     * @see    SpicaPageLayout#setBody()
     * @return SpicaTemplateRenderableAsBlock
     */
    public function getBody()
    {
        if (null !== $this->_body)
        {
            return $this->_body;
        }

        if (null === $this->_bodyClass)
        {
            return null;
        }

        // check class name ended with Block|5 or BlockGroup|10
        if ('Block' === substr($this->_bodyClass, -5))
        {
            $this->_setBody($this->getBlock($this->_bodyClass));
            return $this->_body;
        }
        elseif ('BlockGroup' === substr($this->_bodyClass, -10))
        {
            $this->_setBody($this->getBlockGroup($this->_bodyClass));
            return $this->_body;
        }

        throw new InvalidArgumentException('The class name that is passed to the method SpicaPageLayout::setBody() must end with Block or BlockGroup. ');
    }

    /**
     * Registers a block that is responsible for providing page body content.
     *
     * @see   SpicaPageLayout#setBody()
     * @param SpicaTemplateRenderableAsBlock $block
     */
    protected function _setBody(SpicaTemplateRenderableAsBlock $block)
    {
        $this->_body = $block;
    }

    /**
     * Gets the template output of the page block or page block group that is
     * registered previously in master controller as a placeholder for
     * page body content.
     *
     * @see    SpicaPageLayout#setBody()
     * @return string|null
     */
    public function fetchBody()
    {
        Spica::publishEvent('spica_layout_fetch_body', $this);

        // Above event can trigger one or more callback/closures that can
        // possibly prepare precompiled data (or cached data) for this block
        if (true === isset($this->precompiledContent['body_content']))
        {
            return $this->precompiledContent['body_content'];
        }

        try
        {
            $body = $this->getBody();
            return (null !== $body) ? $body->output() : null;
        }
        catch (Exception $e)
        {
            Spica::logException($e);
            return get_class($e).': '.$e->getMessage();
        }
    }

    /**
     * Registers a template placeholder with current theme
     *
     * @param string $name
     * @param string $type The type of placeholder: "block", "blockgroup"
     */
    public function setPlaceholder($name, $type)
    {
        $this->_placeholders[$name] = $this->getPlaceholder($name, $type);
    }

    /**
     * Fetches template output of a content placeholder which is either a block or a block group.
     *
     * @param  string $name
     * @return string|false
     */
    public function fetchPlaceholder($name)
    {
        if (true === isset($this->_placeholders[$name]))
        {
            return $this->_placeholders[$name]->output();
        }

        return false;
    }

    /**
     * Gets an object of <code>SpicaTemplateRenderableAsBlock</code> by a conventional name.
     *
     * @throws InvalidArgumentException
     * @param  string $className
     * @param  string $type Block type: "block" or "blockgroup"
     * @return SpicaTemplateRenderableAsBlock
     */
    public function getPlaceholder($className, $type)
    {
        switch ($type)
        {
            case 'block':
                return $this->getBlock($className);

            case 'blockgroup':
                return $this->getBlockGroup($className);

            default:
                throw new InvalidArgumentException('The value assigned to the second argument to '.__METHOD__.' is not supported: '.$type);
        }
    }

    /**
     * Registers name of the file that contains CSS file include tags
     * {<link rel="stylesheet" type="text/css" />} that will be used later
     * in the master page.
     *
     * Css file format: css1.tpl.php
     *
     * @param string $fileName File name without extension
     */
    public function attachCss($fileName)
    {
        $this->_cssFiles[] = str_replace(array('/', '\\'), '', $fileName);
    }

    /**
     * Gets the CSS markup tags or CSS file link
     * {<link rel="stylesheet" type="text/css" />} that are part of the page.
     *
     * @return mixed bool|string
     */
    public function loadCss()
    {
        $failed = $this->_loadHeaderFiles($this->_cssFiles, 'css');

        if (count($failed) > 0)
        {
            $dispatcher = SpicaContext::getDispatcher();
            Spica::logError(get_class($dispatcher->getLastController()).':'.$dispatcher->getLastMethod().'(): Some layout css include file can not be found: '.implode(', ', $failed));
            return false;
        }

        return false;
    }

    /**
     * Registers name of the file that contains <script> tags that will be used
     * later in the master page.
     *
     * Script file format: script1.tpl.php
     *
     * @param string $fileName File name without extension
     */
    public function attachScript($fileName)
    {
        $this->_scriptFiles[] = str_replace(array('/', '\\'), '', $fileName);
    }

    /**
     * Includes files that contains script tags such as
     * {<script type="text/javascript"></script>} as part of the page.
     *
     * @return bool|string
     */
    public function loadScript()
    {
        $failed = $this->_loadHeaderFiles($this->_scriptFiles, 'js');

        if (count($failed) > 0)
        {
            $dispatcher = SpicaContext::getDispatcher();
            Spica::logError(get_class($dispatcher->getLastController()).':'.$dispatcher->getLastMethod().'(): One or more script tag include files can not be found: '.implode(', ', $failed));
            return false;
        }
    }

    /**
     * Registers name of the file that contains <meta> tags that will be used
     * later in the master page.
     *
     * File format: meta1.tpl.php
     *
     * @param string $fileName File name without extension
     */
    public function attachMeta($fileName)
    {
        $this->_metaFiles[] = str_replace(array('/', '\\'), '', $fileName);
    }

    /**
     * Includes files that contains meta tags such as {<meta name="description" />}
     * as part of the page.
     *
     * @return bool|string
     */
    public function loadMeta()
    {
        $failed = $this->_loadHeaderFiles($this->_metaFiles, 'meta');

        if (count($failed) > 0)
        {
            $dispatcher = SpicaContext::getDispatcher();
            Spica::logError(get_class($dispatcher->getLastController()).':'.$dispatcher->getLastMethod().'(): some layout meta tag include files can not be found: '.implode(', ', $failed));
            return false;
        }
    }

    /**
     * Registers a page filter that works on the final HTML content of the page.
     *
     * @param SpicaTemplateOutputFilter $filter
     */
    public function addPageOutputFilter($filter)
    {
        $this->_pageFilters[spl_object_hash($filter)] = $filter;
    }

    /**
     * Unsets the current page output filter.
     */
    public function unsetPageOutputFilter()
    {
        $this->_pageFilters = null;
    }

    /**
     * Gets set page output filters.
     *
     * @return array|null
     */
    public function getPageOutputFilters()
    {
        return $this->_pageFilters;
    }

    /**
     * Loads header files into the layout page.
     *
     * @param  array $files
     * @param  string $directory
     * @return array A set of files that could not be loaded
     */
    protected function _loadHeaderFiles($files, $directory)
    {
        $path   = $this->_basePath.'/'.$directory.'/';
        $failed = array();

        foreach ($files as $fileName)
        {
            // Secure file name (dot-dot-slash/directory traversal/directory climbing attack)
            $fileName = str_replace(array("\0", '..', '%'), '', $fileName);
            $filePath = $path.$fileName.'.tpl.php';

            if (false === is_file($filePath))
            {
                $failed[] = $filePath;
                continue;
            }

            include $filePath;
        }

        return $failed;
    }
}

/**
 * SpicaHtmlInjector module offers a transparent embedding of JavaScript or CSS code snippet into HTML pages (not HTML blocks)
 * that are returned by SpicaTemplate/SpicaTheme {@see SpicaPageLayout::addPageOutputFilter()}.
 *
 * @category   spica
 * @package    core
 * @subpackage view
 * @author     Pham Cong Dinh <pcdinh at phpvietnam dot net>
 * @since      Version 0.3
 * @since      May 29, 2010
 * @copyright  Pham Cong Dinh (http://www.phpvietnam.net)
 * @license    http://www.gnu.org/licenses/lgpl-3.0.txt
 * @version    $Id: Template.php 1869 2011-01-07 18:55:25Z pcdinh $
 */
class SpicaHtmlInjector implements SpicaTemplateOutputFilter
{
    /**
     * List of CSS codes or links to add before closing head tag.
     *
     * @var array
     */
    private $_css = array();

    /**
     * List of JS codes or links to add before closing head tag.
     *
     * @var array
     */
    private $_headJs = array();

    /**
     * List of JS codes or links to add before closing body tag.
     *
     * @var array
     */
    private $_endJs = array();

    /**
     * Singleton instance of <code>SpicaHtmlInjector</code>.
     *
     * @var SpicaHtmlInjector
     */
    private static $_instance;

    /**
     * Constructs an object of <code>SpicaHtmlInjector</code>.
     */
    public function __construct()
    {

    }

    /**
     * Creates and manages a singleton object of <code>SpicaHtmlInjector</code>.
     *
     * @return SpicaHtmlInjector
     */
    public static function factory()
    {
        if (null === self::$_instance)
        {
            self::$_instance = new self();
        }

        return self::$_instance;
    }

    /**
     * Adds a CSS link just before closing head tag.
     *
     * @param string $path Relative or absolute HTTP path to CSS file
     * @param string $media Defaults to 'all'
     */
    public function addCssLink($path, $media = 'all')
    {
        $this->_css[] = '<link type="text/css" rel="stylesheet" href="' . $path . '" media="' . $media . '" />';
    }

    /**
     * Adds a CSS code snippet just before closing head tag.
     *
     * @param string $code
     */
    public function addCss($code)
    {
        $this->_css[] = '<style type="text/css">' . $code . '</style>';
    }

    /**
     * Adds a JavaScript link before closing head tag.
     *
     * @param string $path Relative or absolute HTTP path to JavaScript file
     */
    public function addHeadJsLink($path)
    {
        $this->_headJs[] = '<script type="text/javascript" language="JavaScript" src="' . $path . '"></script>';
    }

    /**
     * Adds a JavaScript code snippet before closing head tag.
     *
     * @param string $code
     */
    public function addHeadJs($code)
    {
        $this->_headJs[] = '<script type="text/javascript" language="JavaScript">' . $code . '</script>';
    }

    /**
     * Adds a JavaScript link just before closing body tag.
     *
     * @param string $path Relative or absolute HTTP path to JavaScript file
     */
    public function addEndJsLink($path)
    {
        $this->_endJs[] = '<script type="text/javascript" language="JavaScript" src="' . $path . '"></script>';
    }

    /**
     * Adds a JavaScript code snippet just before closing body tag.
     *
     * @param string $code
     */
    public function addEndJs($code)
    {
        $this->_endJs[] = '<script type="text/javascript" language="JavaScript">' . $code . "\n". '</script>'."\n";
    }

    /**
     * Adds required CSS and JavaScript code snippet on page's HTML content.
     *
     * @param string $content
     */
    public function doFilter($content)
    {
        include_once 'library/spica/core/utils/StringUtils.php';

        $head = '';
        $end  = '';

        if (!empty($this->_css))
        {
            $css = array_unique($this->_css);
            $head = implode("\n", $css);
        }

        if (!empty($this->_headJs))
        {
            $headJs = array_unique($this->_headJs);
            $head .= implode("\n", $headJs);
        }

        if (!empty($this->_endJs))
        {
            $endJs = array_unique($this->_endJs);
            $end = implode("\n", $endJs);
        }

        if (!empty($head))
        {
            $content = SpicaStringUtils::insertBefore($content, '</head>', $head);
        }

        if (!empty($end))
        {
            $content = SpicaStringUtils::insertBefore($content, '</body>', $end);
        }

        return $content;
    }
}

/**
 * This class defines an object class for representing exceptions within the
 * template processing life cycle.
 *
 * namespace spica\core\view\TemplateException;
 *
 * @category   spica
 * @package    view
 * @author     Pham Cong Dinh <pcdinh at phpvietnam dot net>
 * @since      Version 0.3
 * @since      March 31, 2009
 * @copyright  Pham Cong Dinh (http://www.phpvietnam.net)
 * @license    http://www.gnu.org/licenses/lgpl-3.0.txt
 * @version    $Id: Template.php 1869 2011-01-07 18:55:25Z pcdinh $
 */
class SpicaTemplateException extends SpicaException {}

/**
 * Returns the base tag with base application URL.
 *
 * namespace spica\core\view\base_tag;
 *
 * @return string
 */
function spica_view_base_tag()
{
    return '<base href="'.SpicaRequest::getFullBaseUrl().'" />';
}

?>